using System;
using System.Reflection;
using System.Collections.Generic;
using System.Runtime.InteropServices;

using Tao.Lua;
using log4net;

namespace Playtime.RegionModule
{
	public abstract class ClassState<T>
	{
		public abstract string ClassName { get; }

		public static int ReleaseState(IntPtr luaState)
		{
			IntPtr userData = Lua.lua_touserdata(luaState, 1);
			GCHandle.FromIntPtr(Marshal.ReadIntPtr(userData)).Free();
			return 0;
		}

		private static void BoxOntoStack(IntPtr luaState, T o)
		{
			// protect the object from being garbage collected
			GCHandle gch = GCHandle.Alloc(o, GCHandleType.Normal);
			
			// allocates a buffer with just enough room to store a pointer to the managed object
			IntPtr luaBuffer = Lua.lua_newuserdata(luaState, (uint)IntPtr.Size);
			
			// copy the pointer to the managed object into the buffer allocated by lua
			Marshal.WriteIntPtr(luaBuffer, GCHandle.ToIntPtr(gch));
		}

		private static GCHandle UnboxFromStack(IntPtr luaState, int index, string className)
		{
			IntPtr userData = Lua.luaL_checkudata(luaState, index, className);
			Lua.luaL_argcheck(luaState, userData != IntPtr.Zero, index, "'" + className + "' expected");
			return GCHandle.FromIntPtr(Marshal.ReadIntPtr(userData));
		}

		public static void PushOntoLuaStack(IntPtr luaState, T o, string className)
		{
			BoxOntoStack(luaState, o);

			// assign a custom meta-table to our userdata
			Lua.luaL_getmetatable(luaState, className);
			Lua.lua_setmetatable(luaState, -2);
		}

		public static T LoadFromLuaStack(IntPtr luaState, string className, int index)
		{
			return (T)UnboxFromStack(luaState, index, className).Target;
		}

		public static T LoadSelfFromLuaStack(IntPtr luaState, string className)
		{
			return LoadFromLuaStack(luaState, className, 1);
		}
	}
	
	public abstract class LuaClass<T> : Module where T:ClassState<T>
	{
		private static Dictionary<string, Lua.lua_CFunction> mMethods;
		protected static Dictionary<string, Lua.lua_CFunction> Methods
		{
			get
			{ 
				if(mMethods == null)
					mMethods =  new Dictionary<string, Lua.lua_CFunction>();
				return mMethods;
			}
		}

		public void AddMethod(string name, Lua.lua_CFunction func)
		{
			Methods.Add(name, func);
		}
		
		public override void Register(IntPtr luaState)
		{
			base.Register(luaState);
			
			// register methods on the metatable
			if(Methods.Count > 0)
			{
				// if no custom __GC method has been supplied we should add it
				if(!mMethods.ContainsKey("__gc"))
				{
					mMethods.Add("__gc", ClassState<T>.ReleaseState);
				}
					
				Lua.luaL_Reg[] methods = new Lua.luaL_Reg[mMethods.Count+1];
				int cnt=0;
				foreach(KeyValuePair<string, Lua.lua_CFunction> r in mMethods)
				{
					methods[cnt].name = r.Key;
					methods[cnt].func = r.Value;
					++cnt;
				}
				methods[cnt].name = null;		// add termination sentinel
				methods[cnt].func = null;
				Lua.luaL_newmetatable(luaState, ModuleName);
				
				// register the methods on the metatable
				Lua.luaL_register(luaState, null, methods);
				
				// if __index hasn't been defined, we make __index "selfreferencing"
				// make __index on the metatable refer to itself
				if(!mMethods.ContainsKey("__index"))
				{
					Lua.lua_pushstring(luaState, "__index");
	      			Lua.lua_pushvalue(luaState, -2);  /* pushes the metatable */
	      			Lua.lua_settable(luaState, -3);  /* metatable.__index = metatable */
				}
				
				// cleanup the metatable from the stack
				Lua.lua_pop(luaState, 1);
			}
		}
	}
}
